﻿using CarparkEnum;
using CarparkIMS_Model;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using LogUtility;
using log4net.Repository.Hierarchy;
using Microsoft.Extensions.DependencyInjection;
using System.Configuration;
using Microsoft.Extensions.Options;
using System.ComponentModel;
using Common;
using Quartz;
using System.Threading.Tasks;

namespace CarparkBusiness
{
    public partial class ParkingPlaceUnitCalc:IDisposable
    {
        private bool _disposed;
        private ParkingSetting ParkingSetting { get; set; }
        private static ILogger<ParkingPlaceUnitCalc> Logger { get; set; }
        private DateTime HistDateBegin { get; set; }

        private DateTime DateNow = DateTime.Now;

        private readonly IMSContext Context;
        private string ParkingPlaceId { get; set; }
        private ParkingNoCalcType ParkingNoCalcType { get; set; }
        private string ParkingPlaceSharpNumber { get; set; }
        private string NumberPlate { get; set; }
        private long IntervalSeconds { get; set; }
        private long AllowanceSeconds { get; set; }
        private DateTime? StartDatetime { get; set; }
        private long LogIdOfStart { get; set; }
        private DateTime? EndDateTime { get; set; }
        private long LogIdOfEnd { get; set; }
        private List<llqqPlateLog> TimeLogSection { get; set; }
        private List<llqqNumberPlate> NumberPlateList { get; set; }
        private DateTime? LastSetionEndTime { get; set; }
        private TimeSpan DurationTimespan { get; set; }
        private int MaxIntervalTimes { get; set; }  //MaxIntervalTimes default value = 10
        private int IntervalTimes { get; set; }  // if has calculated 10 times still not to get the last EndDatetime,then must set the default EndDatetime(System Time). 
        //private bool IsValidStart { get; set; }
        public int IsCompleted { get; set; }  // if no EndDatetime then equal to false otherwise true 
        public bool Invalid { get; set; } = false;  // if no StartDatetime then equal to true
        public bool IsUpdate { get; set; } = false;  // if StartDatetime is exists in the database then to update
        public bool SingleEnd { get; set; } = false; // this Case is lane the EndDatetime.
        public ParkingPlaceUnitCalc(IMSContext context, ParkingNoCalcType parkingNoCalcType, string parkingPlaceId,long intervalSeconds, long allowanceSeconds,int maxIntervalTimes,int intervalTimes)
        { 
            var services = new ServiceCollection();
            TaskStartUp.ConfigureServices(services); 
            var serviceProvider = services.BuildServiceProvider();
            Logger = serviceProvider.GetService<ILogger<ParkingPlaceUnitCalc>>();
            Logger.LogInformation("Hello world!");
            
            if (ParkingSetting.Get() == null)
            {
                ParkingSetting = serviceProvider.GetService<IOptions<ParkingSetting>>().Value;
            }
            else
            {
                ParkingSetting = ParkingSetting.Get();
            } 
            //initialized
            ParkingNoCalcType = parkingNoCalcType;
            TimeLogSection = new List<llqqPlateLog>();
            NumberPlateList = new List<llqqNumberPlate>(); 
            //---------------------------------------------------------------------------------------
            Context = context;
            Logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss fff}] [FUNC::ParkingPlaceUnitCalc][UNIT CALC INITIALIZEING......]");

            IntervalSeconds = intervalSeconds;
            AllowanceSeconds = allowanceSeconds;
            MaxIntervalTimes = maxIntervalTimes;
            IntervalTimes = intervalTimes;
            ParkingPlaceId = parkingPlaceId;
            ParkingPlaceSharpNumber = Context.llqqCameraDoorMapping.Where(c => c.ParkingPlaceId.Contains(parkingPlaceId)).FirstOrDefault()?.ParkingPlaceSharpNumber ?? "";

            //-----------------
            HistDateBegin = GetHistDateBegin(parkingPlaceId);

            //-----------------
            GetStartDatetime();

            GetNumberPlateList();

            GetTimeLogSection();

            GetEndDatetime();

            IsCompleted = ChkIsCompleted();

            ChkValid();

            DurationTimespan = CalcDuration();
             
            string loggerLine = $" [FUNC::ParkingPlaceUnitCalc][ParkingPlaceUnitCalc()][UNIT CALC INITIALIZEING......][START:{StartDatetime:yyyy-MM-dd HH:mm:ss fff}/{LogIdOfStart}][EndDateTime:{EndDateTime:yyyy-MM-dd HH:mm:ss fff}/{LogIdOfEnd}][IsCompleted={IsCompleted}][Invalid={Invalid}]";
            
            Logger.LogWarning(loggerLine);

            CommonBase.OperateDateLoger(loggerLine);
        }
        public void GetNumberPlateList()
        {
            NumberPlateList = Context.llqqNumberPlate.Where(c=>c.ParkingPlaceId.Contains(ParkingPlaceId) 
                                            && c.ParkingNoCalcType==(int)ParkingNoCalcType //&& c.StartDatetime >= HistDateBegin 
                                            && c.IsCompleted ==(int)ParkingNumberPlateEnum.NOT_COMPLETED 
                                            && c.IsCompleted != (int)ParkingNumberPlateEnum.NO_COMPLETED_STATUS)
                                            .OrderBy(c=>c.StartDatetime).ToList();
        }
        public void GetTimeLogSection()
        {
            List<llqqPlateLog> timeLogSectionConfused = new List<llqqPlateLog>();

            if (StartDatetime == null)
            {
                timeLogSectionConfused = Context.llqqPlateLog.Where(c => c.ParkingPlaceId.Contains(ParkingPlaceId) 
                                && c.OccuredDatetime >= HistDateBegin).OrderBy(c => c.OccuredDatetime).ToList();
            }
            else
            {
                DateTime dStart = StartDatetime.GetValueOrDefault(); 
                long lStart = DatetimeHelper.ToUnixTimeSeconds(dStart);

                var  fanDataStart = Context.llqqPlateLog.Where(c => c.ParkingPlaceId.Contains(ParkingPlaceId) && c.plate.Equals(NumberPlate)
                                                                && c.OccuredDatetime >= dStart.Date)
                                                                .OrderBy(c => c.OccuredDatetime).ToList();

                foreach (var item in fanDataStart)
                {
                    long occur = DatetimeHelper.ToUnixTimeSeconds(item.OccuredDatetime);
                    if (occur >= lStart)
                    {
                        timeLogSectionConfused.Add(item);
                    }
                } 
            }
            //TimeLogSection
            if (timeLogSectionConfused.Count >= 1)  
            {
                TimeLogSection = GetContinuousSetion(timeLogSectionConfused);
            }
            else
            {
                TimeLogSection = null;
            }
            //TimeLogSection = GetContinuousSetion(timeLogSectionConfused);
        }
        public void GetStartDatetime()
        { 
            DateTime? existStartDatetime = CurNotCompletePlate(out long logIdx, out string numPlate);
            if (existStartDatetime != null)
            {
                StartDatetime = existStartDatetime.GetValueOrDefault();
                LogIdOfStart = logIdx;
                NumberPlate = numPlate;
                IsUpdate = true;
            }
            else
            {
                IsUpdate = false;

                DateTime? theLastFirst = LastNumberPlateThenFirst(out long LogId, out string numberPlate);

                if (theLastFirst != null)
                {
                    StartDatetime = theLastFirst;
                    if (LogId != 0)
                    {
                        LogIdOfStart = LogId;
                        NumberPlate = numberPlate;
                    }
                }
                else
                {
                    double allowIntervalSeconds = IntervalSeconds + AllowanceSeconds;
                    // DateTime surposeLogTime = DateNow.AddSeconds(-allowIntervalSeconds); // c.OccuredDatetime > surposeLogTime &&
                    // DateTime surposeLogTime = DateNow.AddSeconds(-allowIntervalSeconds); // c.OccuredDatetime > surposeLogTime &&
                    //var plateLog = Context.llqqPlateLog.Where(c => c.ParkingPlaceId.Contains(ParkingPlaceId)).OrderBy(c => c.OccuredDatetime).Take(1).ToList();

                    var plateLog = Context.llqqPlateLog.Where(c => c.ParkingPlaceId.Contains(ParkingPlaceId) && c.OccuredDatetime > HistDateBegin).OrderBy(c => c.OccuredDatetime).ToList();
                    //var plateLog = Context.llqqPlateLog.Where(c => c.ParkingPlaceId.Contains(ParkingPlaceId)).OrderBy(c => c.OccuredDatetime).Take(1).ToList();

                    if (plateLog.Any())
                    {
                        var surposeTheFirstObj = plateLog.FirstOrDefault();
                        int surposeTheFirstId = surposeTheFirstObj.ID;
                        bool surposeNotOccupied = Context.llqqNumberPlate.Where(c => c.ParkingPlaceId.Contains(ParkingPlaceId) && c.LogIdOfStart == surposeTheFirstId).Any();

                        if (!surposeNotOccupied)
                        {
                            StartDatetime = surposeTheFirstObj.OccuredDatetime;
                            LogIdOfStart = surposeTheFirstObj.ID;
                            Invalid = false;
                            numberPlate = surposeTheFirstObj.plate;
                            NumberPlate = numberPlate;
                            IsCompleted = (int)ParkingNumberPlateEnum.NOT_COMPLETED;
                        }
                    }
                    else
                    {
                        Invalid = true;
                        IsCompleted = (int)ParkingNumberPlateEnum.NO_COMPLETED_STATUS;
                        StartDatetime = null;
                    }
                }
            }  
        } 
        public void GetEndDatetime()
        {
            if (TimeLogSection == null || TimeLogSection?.Count() <= 1)  
            {
                LastSetionEndTime = null; 
                LogIdOfEnd = 0;

                int drang = (int)IntervalSeconds + (int)AllowanceSeconds;  //(int)IntervalSeconds * IntervalTimes + (int)AllowanceSeconds;

                DateTime lastTimesCalcTime = DateNow.AddSeconds(-drang);  //DateNow.AddSeconds(-IntervalSeconds); 

                if (StartDatetime < lastTimesCalcTime && TimeLogSection!=null)
                {
                    var theLast = TimeLogSection.Last();
                    LastSetionEndTime = theLast.OccuredDatetime;
                    EndDateTime = LastSetionEndTime;
                    LogIdOfEnd = theLast.ID;
                    Invalid = false;
                    DurationTimespan = theLast.OccuredDatetime.Subtract(StartDatetime.GetValueOrDefault());
                    IsCompleted = (int)ParkingNumberPlateEnum.IS_COMPLETED; 
                }
            }
            else
            {
                var theLast = TimeLogSection.Last();

                DateTime theLastOccur = theLast.OccuredDatetime;

                int drang = (int)IntervalSeconds  + (int)AllowanceSeconds; //(int)IntervalSeconds * IntervalTimes + (int)AllowanceSeconds;

                DateTime criticalPointDatetime = theLastOccur.AddSeconds(drang);
                //[2022-12-21 10:07:47 108]
                //[[FUNC::ParkingPlaceUnitCalc.GetEndDatetime][EndDateTime:2022-12-21 10:06:41][LogId:6181]
                //[IsCompleted:1][criticalPointDatetime:10:05:36 527 < DateNow:10:07:47 090]]
                int intervalMintues = ParkingSetting.IntervalMinutes;
                criticalPointDatetime = criticalPointDatetime.AddMinutes(intervalMintues); //  

                if (criticalPointDatetime < DateNow)
                {
                    LastSetionEndTime = theLast.OccuredDatetime;
                    EndDateTime = LastSetionEndTime;
                    LogIdOfEnd = theLast.ID;
                    Invalid = false;
                    IsCompleted = (int)ParkingNumberPlateEnum.IS_COMPLETED;

                    string loggerLine = $"[FUNC::ParkingPlaceUnitCalc.GetEndDatetime][EndDateTime:{EndDateTime:yyyy-MM-dd HH:mm:ss}][LogId:{LogIdOfEnd}][IsCompleted:{IsCompleted}][criticalPointDatetime:{criticalPointDatetime:HH:mm:ss fff} < DateNow:{DateNow:HH:mm:ss fff}]";
                    Logger.LogWarning(loggerLine);
                    CommonBase.OperateDateLoger(loggerLine);
                } 
            }
        }

        public bool ChkValidStart(int MinTimesValid)
        {
            int startTimespan = (int)ParkingSetting.IntervalSeconds * MinTimesValid;
            bool isValidStart = false;
            if (TimeLogSection?.Count() > 0)
            {
                var theFirst = TimeLogSection.First().OccuredDatetime;
                var theFirstEnd = theFirst.AddSeconds(startTimespan);
                var firstLogSection = TimeLogSection.Where(c=>c.OccuredDatetime >= theFirst && c.OccuredDatetime <= theFirstEnd);

                if (firstLogSection.Count()>= MinTimesValid)
                {
                    isValidStart = true;
                }
            }
            return isValidStart;
        }

        public List<llqqPlateLog> GetContinuousSetionV0(List<llqqPlateLog> timeLogSectionConfused)
        {
            List<llqqPlateLog> ContinuousTimeLogSection = new List<llqqPlateLog>();
            timeLogSectionConfused = timeLogSectionConfused.OrderBy(c => c.OccuredDatetime).ToList();

            var firstlog = timeLogSectionConfused.First();
            var lastlog = timeLogSectionConfused.Last();

            ContinuousTimeLogSection.Add(firstlog);

            int times = 0;
            DateTime intervalDatetime = firstlog.OccuredDatetime;
            int j = 0;
            foreach (var item in timeLogSectionConfused)
            {
                int drang = (int)IntervalSeconds*j + (int)AllowanceSeconds;
                DateTime drangStart = intervalDatetime.AddSeconds(-drang);
                DateTime drangEnd = intervalDatetime.AddSeconds(drang);
                if (item.plate.Contains(firstlog.plate) && item.ParkingPlaceId.Contains(ParkingPlaceId))
                {
                    if(item.OccuredDatetime > drangStart && item.OccuredDatetime < drangEnd)
                    {
                        Logger.LogInformation($"{drangStart:yyyy-MM-dd HH:mm:ss} {item.OccuredDatetime:yyyy-MM-dd HH:mm:ss} {drangEnd:yyyy-MM-dd HH:mm:ss}");
                        ContinuousTimeLogSection.Add(item);
                    }
                }
                else
                {
                    if (times < IntervalTimes)  
                    {
                        times++;
                        continue;
                    }
                    else
                    {
                        EndDateTime = item.OccuredDatetime;
                        LogIdOfEnd = item.ID;
                        IsCompleted = (int)ParkingNumberPlateEnum.IS_COMPLETED;
                        Invalid = false;
                        break;
                    }
                }
                j++;
            }
            return ContinuousTimeLogSection;
        }

        public List<llqqPlateLog> GetContinuousSetionV1(List<llqqPlateLog> timeLogSectionConfused)
        {  
            List<llqqPlateLog> ContinuousTimeLogSection = new List<llqqPlateLog>();
            timeLogSectionConfused = timeLogSectionConfused.OrderBy(c => c.OccuredDatetime).ToList();

            var firstlog = timeLogSectionConfused.First();
            var lastlog = timeLogSectionConfused.Last();

            ContinuousTimeLogSection.Add(firstlog);
             
            DateTime intervalDatetime = firstlog.OccuredDatetime;
            int j = 1;

            //initialized the near two record 
            llqqPlateLog nextOne = new llqqPlateLog();
            llqqPlateLog TheQualifiedOne = firstlog;
            int drang = (int)IntervalSeconds + (int)AllowanceSeconds;
            llqqPlateLog[] arrays = timeLogSectionConfused.ToArray();
            int lenght = arrays.Length;
            foreach (var item in arrays)
            {
                if (j < lenght)
                {
                    nextOne = arrays[j];
                }
                TimeSpan missSpan = TimeSpan.Zero;
                if (nextOne != null)
                {
                    missSpan = nextOne.OccuredDatetime.Subtract(item.OccuredDatetime);
                }

                if (nextOne.plate.Contains(firstlog.plate) && nextOne.ParkingPlaceId.Contains(ParkingPlaceId)
                    && missSpan.TotalSeconds < drang && missSpan > TimeSpan.Zero && nextOne != null)
                {
                    Logger.LogInformation($"[ParkingPlaceId::{item.ParkingPlaceId};plate::{item.plate};OccuredDatetime::{item.OccuredDatetime:yyyy-MM-dd HH:mm:ss}]");
                    ContinuousTimeLogSection.Add(nextOne);
                    TheQualifiedOne = nextOne;
                }
                else
                {
                    DateTime curDatetime = TheQualifiedOne.OccuredDatetime.AddSeconds(drang);

                    if (curDatetime < DateTime.Now)
                    {
                        EndDateTime = TheQualifiedOne.OccuredDatetime;
                        LogIdOfEnd = TheQualifiedOne.ID;
                        IsCompleted = (int)ParkingNumberPlateEnum.IS_COMPLETED;
                        Invalid = false;
                        Logger.LogInformation($"[BREAK][LogIdOfEnd={LogIdOfEnd} {EndDateTime:yyyy-MM-dd HH:mm:ss}]");
                    } 
                    break; 
                }
                j++;
            }
            return ContinuousTimeLogSection;
        }

        //2022-11-23
        public List<llqqPlateLog> GetContinuousSetionV2(List<llqqPlateLog> timeLogSectionConfused)
        {
            int IntervalMinutes = ParkingSetting.IntervalMinutes;

            if (timeLogSectionConfused?.Count() == 0)
                return null;

            List<llqqPlateLog> ContinuousTimeLogSection = new List<llqqPlateLog>();
            timeLogSectionConfused = timeLogSectionConfused.OrderBy(c => c.OccuredDatetime).ToList();

            var firstlog = timeLogSectionConfused.First();
            var lastlog = timeLogSectionConfused.Last();

            ContinuousTimeLogSection.Add(firstlog);

            DateTime intervalDatetime = firstlog.OccuredDatetime;

            //initialized the near two record 
            llqqPlateLog nextOne = new llqqPlateLog();
            llqqPlateLog TheQualifiedOne = new llqqPlateLog();

            int drang = (int)IntervalSeconds + (int)AllowanceSeconds;

            llqqPlateLog[] arrays = timeLogSectionConfused.ToArray();
            int lenght = arrays.Length;

            if (lenght == 1)
                return ContinuousTimeLogSection;

            for (int j = 1; j < lenght; j++)
            {
                llqqPlateLog current = new llqqPlateLog();
                int i = j - 1;
                current = arrays[i];
                nextOne = arrays[j];

                if (current.OccuredDatetime == nextOne.OccuredDatetime) //skip
                    continue;

                TimeSpan missSpan = TimeSpan.Zero;

                if (nextOne != null)
                {
                    missSpan = nextOne.OccuredDatetime.Subtract(current.OccuredDatetime);
                }

                if (nextOne.plate.Contains(firstlog.plate) && nextOne.ParkingPlaceId.Contains(ParkingPlaceId)
                    && missSpan.TotalSeconds < drang && missSpan > TimeSpan.Zero && nextOne != null)
                {
                    Logger.LogInformation($"[ParkingPlaceId::{current.ParkingPlaceId};Plate::{current.plate};ParkingNoCalcType::{current.ParkingNoCalcType};OccuredDatetime::{current.OccuredDatetime:yyyy-MM-dd HH:mm:ss}]");

                    ContinuousTimeLogSection.Add(nextOne);
                    TheQualifiedOne = nextOne;
                }
                else
                {
                    break;
                }
            }

            return ContinuousTimeLogSection;
        }
        //2022-12-6
        public List<llqqPlateLog> GetContinuousSetion(List<llqqPlateLog> timeLogSectionConfused)
        {
            int IntervalMinutes = ParkingSetting.IntervalMinutes;

            if (timeLogSectionConfused?.Count() == 0)
                return null;

            List<llqqPlateLog> ContinuousTimeLogSection = new List<llqqPlateLog>();

            timeLogSectionConfused = timeLogSectionConfused.OrderBy(c => c.OccuredDatetime).ToList();

            var firstlog = timeLogSectionConfused.First();
             
            ContinuousTimeLogSection.Add(firstlog);
            
            llqqPlateLog[] arrays = timeLogSectionConfused.ToArray();
            
            for (int k=0;k< arrays.Length; k++)
            {
                llqqPlateLog current = new llqqPlateLog();

                current = arrays[k];
                 
                bool existWindow = GetWindowValidLog(timeLogSectionConfused, firstlog, current, out llqqPlateLog next);

                if(existWindow)
                {  
                    ContinuousTimeLogSection.Add(next);
                } 
            }

            return ContinuousTimeLogSection;
        }

        public bool GetWindowValidLog(List<llqqPlateLog> timeLogSectionConfused, llqqPlateLog firstLog, llqqPlateLog current, out llqqPlateLog nextOne)
        { 
            if (timeLogSectionConfused == null)
            {
                nextOne = null;
                return false;
            }

            int drang = (int)IntervalSeconds + (int)AllowanceSeconds;
            DateTime windowsTimeSpan = current.OccuredDatetime.AddSeconds(drang);
            List<llqqPlateLog> allowanceLogwindows = timeLogSectionConfused.Where(c=>c.OccuredDatetime > current.OccuredDatetime && c.OccuredDatetime <= windowsTimeSpan).ToList();

            bool anyLogSpan = allowanceLogwindows.Where(c => c.plate.Contains(firstLog.plate)).Any();

            if (anyLogSpan)
            {
                var windowsPlatList = allowanceLogwindows.Where(c => c.plate.Contains(firstLog.plate)).ToList();

                if(windowsPlatList.Count > 0)
                {
                    nextOne = windowsPlatList.Last(); 
                    return true;
                }
                else
                {
                    nextOne = null;
                    return false;
                }
            }
            else
            {
                nextOne = null;
                return false;
            }
        }

        public void GetLastSetionEndTime() 
        {
            if (TimeLogSection == null)
            {
                LastSetionEndTime = null;
                LogIdOfEnd = 0;
            }
            else
            { 
                if (TimeLogSection?.Count()>1)
                {
                    var theLast = TimeLogSection.Last();

                    DateTime theLastOccur = theLast.OccuredDatetime;

                    int drang = (int)IntervalSeconds * IntervalTimes + (int)AllowanceSeconds;

                    DateTime theLastToAwhile = theLastOccur.AddSeconds(drang);

                    if(theLastToAwhile < DateNow)
                    {
                        LastSetionEndTime = theLast.OccuredDatetime;
                        LogIdOfEnd = theLast.ID;
                        Invalid = false;
                        IsCompleted = (int)ParkingNumberPlateEnum.IS_COMPLETED;
                    }
                }
                else
                {
                    LastSetionEndTime = null; 
                    LogIdOfEnd = 0;
                }
            } 
        } 
        
        public DateTime ParseOccuredDatetime(string date)
        {
            DateTime occ = DateTime.Parse(date);
            return occ;
        }
        
        public DateTime? LastNumberPlateThenFirst(out long logIdOfStart,out string numberPlate) 
        {
            logIdOfStart = 0;
            numberPlate = string.Empty;
             
            var NumberPlateListCompleted = Context.llqqNumberPlate.Where(c => 
                                                    c.ParkingPlaceId.Contains(ParkingPlaceId) 
                                                    && c.IsCompleted == (int)ParkingNumberPlateEnum.IS_COMPLETED)
                                                    .OrderBy(c => c.StartDatetime).ToList();
             
            if (NumberPlateListCompleted?.Count()>0)
            {
                var theLast = NumberPlateListCompleted.Last();

                string log = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss fff}] [FUNC::ParkingPlaceUnitCalc.La..ThenFirst][LAST.START:{theLast.StartDatetime}][LAST.END:{theLast.EndDatetime}][COMPLETED][AAAAB]";
                Logger.LogWarning(log);
                CommonBase.OperateDateLoger(log);


                DateTime? theLastEnd = theLast.EndDatetime.GetValueOrDefault();

                if(theLastEnd.Value == DateTime.MinValue)
                {
                    return null;
                }

                if(theLastEnd.GetValueOrDefault().Millisecond > 1)
                {
                    theLastEnd = theLastEnd.Value.AddSeconds(1); //
                } 

                var plateLog = Context.llqqPlateLog.Select(s => new { s.ID,s.plate,s.ParkingPlaceId,s.OccuredDatetime })
                                                    .Where(c => c.OccuredDatetime > theLastEnd 
                                                    && c.ParkingPlaceId.Contains(ParkingPlaceId)).OrderBy(c=>c.OccuredDatetime).ToList();

                if (plateLog.Count()>0)  
                {
                    var theFirstObj = plateLog.First();
                    DateTime theFirst = theFirstObj.OccuredDatetime;
                    logIdOfStart = theFirstObj.ID;
                    numberPlate = theFirstObj.plate;

                    string newlog = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss fff}] [FUNC::ParkingPlaceUnitCalc.La..ThenFirst.plateLog][plateLog.logIdOfStart:{logIdOfStart}{theFirst:MM-dd HH:mm:ss fff}][AAAAB]";
                    Logger.LogWarning(newlog);
                    CommonBase.OperateDateLoger(newlog);

                    return theFirst;
                }
            }
            else  //2022-12-20
            {
                var plateLog = Context.llqqPlateLog.Select(s => new { s.ID, s.plate, s.ParkingPlaceId, s.OccuredDatetime })
                                                   .Where(c => c.OccuredDatetime > HistDateBegin
                                                   && c.ParkingPlaceId.Contains(ParkingPlaceId)).OrderBy(c => c.OccuredDatetime).ToList();

                if (plateLog.Count() > 0)
                {
                    var theFirstObj = plateLog.First();
                    DateTime theFirst = theFirstObj.OccuredDatetime;
                    logIdOfStart = theFirstObj.ID;
                    numberPlate = theFirstObj.plate;

                    string newlog = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss fff}] [FUNC::Last..ThenFirst.plateLog][plateLog.logIdOfStart:{logIdOfStart}{theFirst:MM-dd HH:mm:ss fff}][AAAAC][LOG BY HistDateBegin]";
                    Logger.LogWarning(newlog);
                    CommonBase.OperateDateLoger(newlog); 
                    return theFirst;
                }
                else
                {
                    return HistDateBegin;  //2022-12-21
                }
            }

            return null;
        }

        //========================================================================================
        private DateTime GetHistDateBegin(string parkingPlaceId)
        {
            if (ParkingSetting.HistDateBegin > 0)
            {
                HistDateBegin = DateNow.AddMinutes(-ParkingSetting.HistDateBegin);
            }

            //I
            int intNotCompleted = (int)ParkingNumberPlateEnum.NOT_COMPLETED; 
            var notCompletedPlates = Context.llqqNumberPlate.Select(s => new { s.ID,s.ParkingPlaceId,s.NumberPlate,s.StartDatetime,s.EndDatetime,s.IsCompleted})
                .Where(c => c.ParkingPlaceId.Equals(parkingPlaceId) 
                && c.IsCompleted == intNotCompleted).OrderBy(c => c.StartDatetime);

            if(notCompletedPlates.Any())
            {
                var theLastNotCompleted = notCompletedPlates.Last();
                if(theLastNotCompleted.EndDatetime!=null)
                {
                    return theLastNotCompleted.EndDatetime.GetValueOrDefault();
                }
                else
                {
                    return theLastNotCompleted.StartDatetime;
                }
            }

            //II
            int IS_Completed = (int)ParkingNumberPlateEnum.IS_COMPLETED;
            var CompletedPlates = Context.llqqNumberPlate.Select(s => new { s.ID, s.ParkingPlaceId, s.NumberPlate, s.StartDatetime, s.EndDatetime, s.IsCompleted })
                .Where(c => c.ParkingPlaceId.Equals(parkingPlaceId)
                && c.IsCompleted == IS_Completed).OrderBy(c => c.StartDatetime);

            if (CompletedPlates.Any())
            {
                var theLastCompleted = CompletedPlates.Last();
                if (theLastCompleted.EndDatetime != null)
                {
                    return theLastCompleted.EndDatetime.GetValueOrDefault();
                }
                else
                {
                    return theLastCompleted.StartDatetime;
                }
            }

            //III
            var x = DateNow.AddMinutes(-ParkingSetting.HistDateBegin);
            return x;
        }
        public DateTime? CurNotCompletePlate(out long logIdOfStart, out string numberPlate)
        {
            logIdOfStart = 0;
            numberPlate = string.Empty;
            int intNotCompleted = (int)ParkingNumberPlateEnum.NOT_COMPLETED;
            var notCompletedPlates = Context.llqqNumberPlate.Where(c => c.ParkingPlaceId.Equals(ParkingPlaceId) 
            && c.IsCompleted == intNotCompleted).OrderBy(c => c.StartDatetime).ToList(); 

            if (notCompletedPlates?.Count() > 0)
            {
                var theFirst = notCompletedPlates.First();

                DateTime theStartDatetime = theFirst.StartDatetime;

                logIdOfStart = theFirst.LogIdOfStart;

                if (theStartDatetime == DateTime.MinValue || theStartDatetime == DateTime.MaxValue || logIdOfStart == 0)
                {
                    return null;
                }
                 
                var theFirstObj = Context.llqqPlateLog.FindAsync((int)logIdOfStart).Result;

                if (theFirstObj != null)
                { 
                    DateTime theStart = theFirstObj.OccuredDatetime;
                    logIdOfStart = theFirstObj.ID;
                    numberPlate = theFirstObj.plate;
                    return theStart;
                }
            }
            return null;
        }
     
        public TimeSpan CalcDuration()
        {
            TimeSpan duraSpan = TimeSpan.Zero;
            if(StartDatetime.HasValue && EndDateTime.HasValue)
            {
                duraSpan = EndDateTime.GetValueOrDefault().Subtract(StartDatetime.Value);
            }
            return duraSpan;
        }
        public void ChkValid()
        {
            if(StartDatetime != null && !string.IsNullOrEmpty(NumberPlate))
            {
                Invalid = false;
            }
            else
            {
                Invalid =true;
            }
        }
        public int ChkIsCompleted()
        {
            int _IsCompleted = (int)ParkingNumberPlateEnum.NOT_COMPLETED;
            if (StartDatetime != null && EndDateTime !=null)
            {
                _IsCompleted = (int)ParkingNumberPlateEnum.IS_COMPLETED;
            }
            else if (StartDatetime != null && EndDateTime == null)
            {
                _IsCompleted = (int)ParkingNumberPlateEnum.NOT_COMPLETED;
            }
            else if (StartDatetime == null)
            {
                _IsCompleted = (int)ParkingNumberPlateEnum.NO_COMPLETED_STATUS;
            } 
            return _IsCompleted;
        }
         
        //SAVE
        public async Task InsetNumberPlate(ParkingPlaceUnitCalc placeUnitCalc)
        {
            DateTime dt = DateTime.Now;
            llqqNumberPlate llqqNumberPlate = new llqqNumberPlate
            {
                ParkingNoCalcType = (int)placeUnitCalc.ParkingNoCalcType,
                NumberPlate = placeUnitCalc.NumberPlate,
                ParkingPlaceId = placeUnitCalc.ParkingPlaceId,
                ParkingPlaceSharpNumber = placeUnitCalc.ParkingPlaceSharpNumber,
                LogIdOfStart = (int)placeUnitCalc.LogIdOfStart,
                StartDatetime = placeUnitCalc.StartDatetime.GetValueOrDefault(),
                IntervalTimes = 1, //for insert
                LogIdOfEnd = 0,
                EndDatetime = null,
                DurationTimespan = (long)placeUnitCalc.DurationTimespan.TotalSeconds,
                CreatedOn = dt,
                UpdatedOn = dt,
                IsCompleted = placeUnitCalc.IsCompleted
            };

            if (placeUnitCalc.EndDateTime == null)
            {
                llqqNumberPlate.LogIdOfEnd = 0;
                llqqNumberPlate.EndDatetime = null;
            }
            else if (placeUnitCalc.LogIdOfEnd > 0 && placeUnitCalc.EndDateTime != null)
            {
                llqqNumberPlate.LogIdOfEnd = (int)placeUnitCalc.LogIdOfEnd;
                llqqNumberPlate.EndDatetime = placeUnitCalc.EndDateTime.GetValueOrDefault();
            }

            if (CheckLogIdOfStartExist(placeUnitCalc.LogIdOfStart) == false)
            {
                Context.Add(llqqNumberPlate);
                await Context.SaveChangesAsync();
            }
            await Task.CompletedTask;
        }

        private bool CheckLogIdOfStartExist(long logIdOfStart)
        {
            if (logIdOfStart == 0)
                return false;

            var existed = Context.llqqNumberPlate.Where(c => c.LogIdOfStart == logIdOfStart).Any();

            return existed;
        }

        public async Task UpdateNumberPlate(ParkingPlaceUnitCalc placeUnitCalc)
        {
            DateTime dt = DateTime.Now;

            //case of danjiao
            if (placeUnitCalc.EndDateTime != null && placeUnitCalc.IsUpdate == true
                && placeUnitCalc.LogIdOfEnd != 0 )
            {
                var record1 = Context.llqqNumberPlate.Where(c => c.ParkingPlaceId.Equals(placeUnitCalc.ParkingPlaceId)
                                && c.LogIdOfStart == placeUnitCalc.LogIdOfStart).FirstOrDefault();

                record1.IntervalTimes++;

                if (placeUnitCalc.EndDateTime != null)
                {
                    record1.EndDatetime = placeUnitCalc.EndDateTime.GetValueOrDefault();

                    if (placeUnitCalc.IsCompleted != (int)ParkingNumberPlateEnum.NO_COMPLETED_STATUS)
                    {
                        record1.DurationTimespan = (long)placeUnitCalc.DurationTimespan.TotalSeconds;
                    }
                }

                if (placeUnitCalc.LogIdOfEnd > 0)
                {
                    record1.LogIdOfEnd = (int)placeUnitCalc.LogIdOfEnd;
                }

                record1.IsCompleted = placeUnitCalc.IsCompleted;
                record1.UpdatedOn = dt;
                Context.Update(record1);
                await Context.SaveChangesAsync();
            }

            if (placeUnitCalc.EndDateTime == null && placeUnitCalc.LogIdOfEnd == 0 && placeUnitCalc.TimeLogSection?.Count() <= 1)
            {
                var rec1 = Context.llqqNumberPlate.Where(c => c.ParkingPlaceId.Equals(placeUnitCalc.ParkingPlaceId)
                                && c.LogIdOfStart == placeUnitCalc.LogIdOfStart).FirstOrDefault();

                if (rec1.IntervalTimes > 1 || rec1.UpdatedOn.Subtract(rec1.CreatedOn) > TimeSpan.FromSeconds(placeUnitCalc.ParkingSetting.IntervalSeconds))
                {
                    rec1.IntervalTimes++;
                    rec1.EndDatetime = rec1.StartDatetime;
                    rec1.LogIdOfEnd = rec1.LogIdOfStart;
                    rec1.UpdatedOn = dt;
                    rec1.IsCompleted = (int)ParkingNumberPlateEnum.NO_COMPLETED_STATUS;

                    Context.Update(rec1);
                    await Context.SaveChangesAsync();
                }
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            // If you need thread safety, use a lock around these 
            // operations, as well as in your methods that use the resource.
            if (!_disposed)
            {
                if (disposing)
                {  
                    Console.Out.WriteLineAsync($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss fff}] [INFO] [new ParkingPlaceUnitCalc::Dispose]");
                }
                Context.Dispose(); //2022-12-21
                NumberPlateList = null;
                TimeLogSection = null;
                _disposed = true;
            }
        }
    }
}
